#pragma once

#include "Dataset/Database.h"
#include "ZhangsFingerRootsDetector.h"
#include "YangsFingerRootsDetector.h"
#include <edcc/facade.h>
#include <edcc/status.h>

#define ASSERT_STATUS_OK(s)  \
    do                       \
    {                        \
        if (!s.IsOk())       \
        {                    \
            perror(s.msg()); \
            return -1;       \
        }                    \
    } while (0)

std::vector<std::string> split(const std::string &str, const std::string &delim);

class Validator
{
private:
    //ugly code,who is responsible for deleting map.values ?
    map<string, int *> fincon_map;
    const string fincon_save_file_path = "/home/yjy/dataset/palmprint_dectection_tongji/LHand/txt_data/Figcon/figCon_nn.txt";

    void get_finger_conner(cv::Mat img, int figCon[6], string &filename) const
    {
        ZhangsFingerRootsDetector *fd = new ZhangsFingerRootsDetector(img);
        fd->detect(filename);
        int flag = fd->getDetectedArr(figCon);
        if (flag == 0)
        {
            figCon[0] = 262;
            figCon[1] = 300;
            figCon[2] = 0;
            figCon[3] = 0;
            figCon[4] = 312;
            figCon[5] = 500;

            // YangsFingerRootsDetector *fd_yang = new YangsFingerRootsDetector(img);
            // fd_yang->detect();
            // int flag = fd_yang->getDetectedArr(figCon);
            // delete fd_yang;
        }
        // adjust coordinates according to device distance
        // figCon[0] += 28;
        // figCon[2] += 28;
        // figCon[4] += 28;

        delete fd;
    }

protected:
    Database &db;

    virtual int *get_fincon_coordinates(const palmprint &p) const
    {
        int *fincon = new int[6];
        string filename(p.filename);
        this->get_finger_conner(p.img, fincon, filename);

        return fincon;
    }

    virtual void get_roi_coordinates(const palmprint &p, int fincon[6], int Sx[4], int Sy[4]) const
    {
        auto get_coo_1 = [this](const palmprint &p, int fincon[6], int Sx[4], int Sy[4]) -> void {
            ROIExtracter roie(fincon);
            const cv::Point *const *result = roie.extract();

            //ROI data [200 149 418 97 469 315 251 366] comply rule:
            //from index finger root, clock wise, to little finger root
            //[w,h,w,h,w,h]

            //Sx Sy comply rule:
            //from little finger root, counter clock wise, to index finger root
            //Sx -> axis height;Sy -> axis width

            Sx[0] = result[0][3].y;
            Sx[1] = result[0][2].y;
            Sx[2] = result[0][1].y;
            Sx[3] = result[0][0].y;

            Sy[0] = result[0][3].x;
            Sy[1] = result[0][2].x;
            Sy[2] = result[0][1].x;
            Sy[3] = result[0][0].x;

            delete[] result[0];
            delete[] result;
        };

        auto get_coo_2 = [this](const palmprint &p, int fincon[6], int Sx[4], int Sy[4]) -> void {
            ROIExtracter roie(fincon);
            roie.extract(Sx, Sy);
        };

        get_coo_1(p, fincon, Sx, Sy);
    }

    virtual uchar *get_roi(const palmprint &p, int Sx[4], int Sy[4]) const
    {
        const int roi_size = 128;
        const float scale_factor = roi_size / (float)p.img.cols;

        uchar *roi_data = new uchar[roi_size * roi_size];
        cv::Mat img, img_gray;

        cv::resize(p.img, img, cv::Size(), scale_factor, scale_factor);
        for (int i = 0; i < 4; i++)
        {
            Sx[i] *= scale_factor;
            Sy[i] *= scale_factor;
        }

        if(img.channels()>2) cv::cvtColor(img, img_gray, cv::COLOR_BGR2GRAY);
	else img_gray=img;
        ROIExtraction(img_gray.data, roi_data, Sx, Sy);

        img_gray.release();
        img.release();

        return roi_data;
    }

    virtual printFeature *getFeature(const palmprint &p, uchar *roi_data) const
    {
        printFeature *pF2D = new printFeature();
        FeatureExtractionPrint(roi_data, pF2D);

        delete[] roi_data;
        return pF2D;
    }

    virtual float match(printFeature *A, printFeature *B) const
    {
        return MatchPrint(A, B);
    }

public:
    Validator(Database &db) : db(db){};

    ~Validator();

    virtual float validate(const palmprint &pp, int suspect_person_id, const float TH = 0.35)
    {
        auto debug = [](uchar *roi_data) -> void {
            cv::Mat img(cv::Size2d(128, 128), CV_8U);
            img.data = roi_data;
            cv::imshow("roi_data", img);
            cv::waitKey(0);
        };

        auto validate_cmpCode = [this](const palmprint &pp, int suspect_person_id, const float TH = 0.35) -> float {
            int Sx[4], Sy[4];
            float score = 0;
            auto pre_data = db.getImages(suspect_person_id);
            //TODO:  rabbish to be deleted!!
            get_roi_coordinates(pp, get_fincon_coordinates(pp), Sx, Sy);
            printFeature *A = getFeature(pp, get_roi(pp, Sx, Sy));
            for (int i = 0; i < pre_data->size(); i++)
            {
                const palmprint &pp_b = (*pre_data)[i];
                get_roi_coordinates(pp_b, get_fincon_coordinates(pp_b), Sx, Sy);
                printFeature *B = getFeature(pp_b, get_roi(pp_b, Sx, Sy));
                score += match(A, B) < TH ? 1 : 0;
                delete B;
            }
            score /= pre_data->size();

            delete A;
            pre_data.reset();

            return score;
        };

        auto validate_EDCC = [this](const palmprint &pp) -> float {
            const float TH = 0.1;
            auto pre_data = db.getImages(pp.person_id);
            const palmprint &pp_b = (*pre_data)[0];
            edcc::Status s;
            // create a new encoder.
            auto inst = edcc::EdccFacade::Instance();
            auto encoder_id = inst->NewEncoderWithConfig(29, 5, 5, 10, &s);
            ASSERT_STATUS_OK(s);
            // encode palmprints to code buffer.
            size_t buffer_size = inst->GetSizeOfCodeBufferRequired(encoder_id);
            char *code_buffer_one = new char[buffer_size];
            char *code_buffer_another = new char[buffer_size];
            inst->EncodePalmprint(encoder_id, pp.img, code_buffer_one, buffer_size, &s);
            ASSERT_STATUS_OK(s);
            inst->EncodePalmprint(encoder_id, pp_b.img, code_buffer_another, buffer_size, &s);
            ASSERT_STATUS_OK(s);
            // calculate the similarity score of two codes.
            double score = inst->CalcCodeSimilarity(code_buffer_one, code_buffer_another, &s);
            ASSERT_STATUS_OK(s);
            std::cout << "matching score=" << score << std::endl;
            printf("%s <-> %s is %s the same individual\n", pp.filename.c_str(), pp_b.filename.c_str(), score > TH ? "true" : "false");

            if (score > TH)
                return 1;
            else
            {
                return 0;
            }
        };

        return validate_cmpCode(pp, suspect_person_id, TH);
    };

    virtual int identify(const palmprint &pp, const float TH = 0.35)
    {
        int Sx[4], Sy[4], suspect_person_id = db.get_first_person(), i = 0;
        float score = 1;
        get_roi_coordinates(pp, get_fincon_coordinates(pp), Sx, Sy);
        printFeature *A = getFeature(pp, get_roi(pp, Sx, Sy));

        do
        {
            auto pre_data = db.getImages(suspect_person_id);
            const palmprint &pp_b = (*pre_data)[0];
            get_roi_coordinates(pp_b, get_fincon_coordinates(pp_b), Sx, Sy);
            printFeature *B = getFeature(pp_b, get_roi(pp_b, Sx, Sy));
            score = match(A, B);
            delete B;

            if (score < TH)
                break;

            suspect_person_id = db.next_person(suspect_person_id);
            i++;
        } while (i < db.person_num());

        return suspect_person_id;
    };
};

Validator::~Validator()
{
    //todo:clear fincon_map
}
